import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author wch
 * @date 2017-7-6
 */
public class HttpUtil {

    private static final String X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8";

    private static final String APPLICATION_JSON = "application/json;charset=UTF-8";

    private static final int SUCCESS = 200;

    private static final String UTF8 = "UTF-8";

    private static RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(15000).setConnectTimeout(15000).setConnectionRequestTimeout(5000).build();


    private static Logger log = LoggerFactory.getLogger(HttpUtil.class);

    /**
     * json提交
     *
     * @param url
     * @param params
     * @return
     */
    public static String postJson(String url, Object params) throws HttpException {
        return postJson(url, params, null);
    }

    /**
     * json提交
     *
     * @param url
     * @param params
     * @param header
     * @return
     */
    public static String postJson(String url, Object params, Map<String, Object> header) throws HttpException {
        return postJson(url, params, header, null);
    }

    public static String postJson(String url, Object params, Map<String, Object> header, ResultCallBack resultCallBack) throws HttpException {
        String data = params instanceof String ? (String) params : JSON.toJSONString(params);
//        String data = JSON.toJSONString(params instanceof String?(String) params:params);
        StringEntity postEntity = new StringEntity(data, UTF8);
        postEntity.setContentType(APPLICATION_JSON);
        return basePost(url, header, postEntity, null, null, resultCallBack);
    }

    /**
     * 表单提交
     *
     * @param url
     * @param o
     * @return
     */
    public static String postForm(String url, Object o) throws HttpException {
        return postForm(url, o, null);
    }

    /**
     * 表单提交
     *
     * @param url
     * @param o
     * @param header
     * @return
     */
    public static String postForm(String url, Object o, Map<String, Object> header) throws HttpException {
        Map<String, Object> params = JSON.parseObject(JSON.toJSONString(o), Map.class);
        return postForm(url, params, header);
    }

    /**
     * 表单提交
     *
     * @param url
     * @param params
     * @return
     */
    public static String postForm(String url, Map<String, Object> params) throws HttpException {
        return postForm(url, params, null);
    }

    /**
     * 表单提交
     *
     * @param url
     * @param params
     * @param header
     * @return
     */
    public static String postForm(String url, Map<String, Object> params, Map<String, Object> header) throws HttpException {
        return postForm(url, params, header, null, null, null);
    }

    public static String postForm(String url, Map<String, Object> params, Map<String, Object> header, ResultCallBack resultCallBack) throws HttpException {
        return postForm(url, params, header, null, null, resultCallBack);
    }

    /**
     * 表单提交
     *
     * @param url
     * @param params
     * @param header
     * @param contentTypeKey
     * @param file
     * @param resultCallBack
     * @return
     */
    private static String postForm(String url, Map<String, Object> params, Map<String, Object> header, String contentTypeKey, File file, ResultCallBack resultCallBack) throws HttpException {
        StringEntity postEntity = buildPostEntity(params);
        return basePost(url, header, postEntity, contentTypeKey, file, resultCallBack);
    }


    private static StringEntity buildPostEntity(Map<String, Object> params) throws HttpException {
        StringEntity postEntity = null;
        if (params != null && params.size() > 0) {
            List<NameValuePair> formParams = getParams(params);
            try {
                postEntity = new UrlEncodedFormEntity(formParams, UTF8);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                throw new HttpException("参数构建异常");
            }
            postEntity.setContentType(X_WWW_FORM_URLENCODED);
        }
        return postEntity;
    }

    /**
     * get方法
     *
     * @param url
     * @return
     */
    public static String get(String url) throws HttpException {
        return get(url, null);
    }

    /**
     * get方法
     *
     * @param url
     * @param header
     * @return
     */
    public static String get(String url, Map<String, Object> header) throws HttpException {

        return baseGet(url, header, (e) -> {
            try {

                return EntityUtils.toString(e.getEntity(), UTF8);
            } catch (IOException e1) {
                e1.printStackTrace();
                throw new HttpException("读写异常");
            }
        });
    }

    /**
     * file以流形式上传
     *
     * @param url
     * @param files
     * @return
     */
    public static String uploadFileStream(String url, Map<String, File> files) throws HttpException {
        return uploadFileStream(url, null, files);
    }

    public static String uploadFileStream(String url, Object o, Map<String, File> files) throws HttpException {
        HttpEntity httpEntity = getHttpEntityFile(files, (builder, key) -> {
            builder.addBinaryBody(key, FileUtil.file2Bytes(files.get(key)), ContentType.DEFAULT_BINARY,
                    files.get(key).getName());
        }, o);
        return uploadFile(url, httpEntity);
    }

    /**
     * file上传
     *
     * @param url
     * @param files
     * @return
     */
    public static String uploadFile(String url, Map<String, File> files) throws HttpException {
        return uploadFile(url, null, files);
    }

    public static String uploadFile(String url, Object params, Map<String, File> files) throws HttpException {
        HttpEntity httpEntity = getHttpEntityFile(files, (builder, key) -> {
            FileBody fileBody = new FileBody(files.get(key));
            builder.addPart(key, fileBody);
        }, params);
        return uploadFile(url, httpEntity);
    }

    private static String uploadFile(String url, HttpEntity httpEntity) throws HttpException {
        return basePost(url, null, httpEntity, null, null, null);
    }

    /**
     * @param url            访问路径
     * @param contentTypeKey 下载文件contentType，包含的key
     * @param file           要保存的文件路径
     * @return
     * @throws HttpException
     */
    public static String getDownLoad(String url, String contentTypeKey, File file) throws HttpException {
        return baseGet(url, null, (h) -> {
            String result = writeFile(h.getEntity(), contentTypeKey, file);
            return result;
        });
    }

    /**
     * @param url  访问路径
     * @param file 要保存的文件路径
     * @return
     * @throws HttpException
     */
    public static String getDownLoad(String url, File file) throws HttpException {
        return getDownLoad(url, null, file);
    }

    /**
     * @param url    访问路径
     * @param o      参数
     * @param header 头
     * @param file   要保存的文件路径
     * @return
     * @throws HttpException
     */
    public static String postFormDownLoad(String url, Object o, Map<String, Object> header, File file) throws HttpException {
        return postFormDownLoad(url, o, header, file, null);
    }

    /**
     * @param url            访问路径
     * @param o              参数
     * @param header         头
     * @param file           要保存的文件路径
     * @param contentTypeKey 下载文件contentType，包含的key
     * @return
     * @throws HttpException
     */
    public static String postFormDownLoad(String url, Object o, Map<String, Object> header, File file, String contentTypeKey) throws HttpException {
        Map<String, Object> params = JSON.parseObject(JSON.toJSONString(o), Map.class);
        return postForm(url, params, header, contentTypeKey, file, null);
    }

    private static String writeFile(HttpEntity h, String contentTypeKey, File file) throws HttpException {
        OutputStream outputStream = null;
        try {
            if (file == null) {
                String result = EntityUtils.toString(h, "UTF-8");
                return result;
            }
            FileUtil.mkdirs(file.getParent());
            outputStream = new FileOutputStream(file);
            if (StringUtils.isEmpty(contentTypeKey)) {
                h.writeTo(outputStream);
                return file.getAbsolutePath();
            }
            ContentType contentType = ContentType.get(h);
            if (contentTypeKey.equals(contentType.getMimeType())) {
                h.writeTo(outputStream);
                return file.getAbsolutePath();
            }
            String result = EntityUtils.toString(h, "UTF-8");
            throw new HttpException(result);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("", e);
            throw new HttpException("写文件异常");
        } finally {
            FileUtil.close(outputStream);
        }
    }

    private static String basePost(String url, Map<String, Object> header, HttpEntity postEntity, String contentTypeKey, File file, ResultCallBack resultCallBack) throws HttpException {
        HttpPost httpPost = new HttpPost(url);
        return baseRequest(header, httpPost, postEntity, resultCallBack != null ? resultCallBack : (e -> {
            String result = writeFile(e.getEntity(), contentTypeKey, file);
            return result;
        }));
    }

    private static String baseGet(String url, Map<String, Object> header, ResultCallBack resultCallBack) throws HttpException {
        HttpGet httpGet = new HttpGet(url);
        return baseRequest(header, httpGet, null, resultCallBack);
    }

    private static String baseRequest(Map<String, Object> header, HttpRequestBase httpRequest, HttpEntity httpEntity, ResultCallBack resultCallBack) throws HttpException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        setHttpEntity(httpRequest, httpEntity);
        setHeader(httpRequest, header);
        setConfig(httpRequest);
        CloseableHttpResponse httpResponse = null;
        try {
//            log.info("http请求时间：{}", DateUtil.dateToString(new Date(),DateUtil.FORMAT_LONG));
            httpResponse = httpClient.execute(httpRequest);
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode != SUCCESS) {
                String errorResult = EntityUtils.toString(httpResponse.getEntity(), UTF8);
                throw new HttpException(errorResult);
            }
            return resultCallBack.callBack(httpResponse);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("", e);
            throw new HttpException("http请求异常");
        } finally {
            FileUtil.close(httpResponse);
            FileUtil.close(httpClient);
        }
    }

    private static void setConfig(HttpRequestBase httpRequest) {
        httpRequest.setConfig(requestConfig);
    }

    private static void setHeader(HttpRequestBase httpRequest, Map<String, Object> header) {
        if (header != null) {
            Header[] heads = getHead(header);
            httpRequest.setHeaders(heads);
        }
    }

    private static void setHttpEntity(HttpRequestBase httpRequest, HttpEntity httpEntity) {
        if (httpRequest instanceof HttpPost) {
            HttpPost httpPost = (HttpPost) httpRequest;
            if (httpEntity != null) {
                httpPost.setEntity(httpEntity);
            }
        }
    }


    private static HttpEntity getHttpEntityFile(Map<String, File> files, HttpParamsTemplate httpParamsTemplate, Object params) throws HttpException {
        if (files == null || files.size() == 0) {
            throw new HttpException("无可上传的文件");
        }
        Map<String, String> map = JSONObject.parseObject(JSON.toJSONString(params), Map.class);
        MultipartEntityBuilder reqEntity = MultipartEntityBuilder.create();
        ContentType strContent = ContentType.create("text/plain", Charset.forName("UTF-8"));
        if (map != null) {
            map.forEach((key, value) -> {
                reqEntity.addTextBody(key, value, strContent);
            });
        }
        files.forEach((key, file) -> {
            httpParamsTemplate.addPart(reqEntity, key);
        });
        return reqEntity.build();
    }

    private static Header[] getHead(Map<String, Object> params) {
        List<Header> heads = new ArrayList<>();
        params.forEach((key, value) -> {
            Header head = new BasicHeader(key, value == null ? null : value.toString());
            heads.add(head);
        });
        return heads.toArray(new Header[heads.size()]);
    }

    private static List<NameValuePair> getParams(Map<String, Object> params) {
        List<NameValuePair> formParams = new ArrayList<>();
        params.forEach((key, value) -> {
            if (value == null) {
                return;
            }
            formParams.add(new BasicNameValuePair(key, value.toString()));
        });
        return formParams;
    }

    private interface HttpParamsTemplate {
        void addPart(MultipartEntityBuilder builder, String key);
    }

    public interface ResultCallBack {
        String callBack(CloseableHttpResponse httpEntity) throws HttpException;
    }
}
